import argparse
import re, time
import socketserver as SocketServer
from collections import deque
import NLAgentDB

# Zero out some memory we can copy to send
empty_bytes = bytearray(1000000)

# Time delta used to determine if two connections with matching tuples are the same.
TIME_DELTA=1000
TIMEOUT = 1000

def parseNumRange(string):
  r = re.match(r'(\d+)(?:-(\d+))?$', string)
  if not r:
    raise argparse.ArgumentTypeError("'" + string + "' is not a range of ports.")
  start = r.group(1)
  end = r.group(2) or start
  return list(range(int(start,10), int(end,10)+1))

def ParseIPList(file, my_group):
  print("Parsing group file ", file)
  ip_list = []
  has_errors = False
  try:
     with open(file) as f:
      lines = f.readlines()
      f.close()
     for line in lines:
      pair = line.strip().split(' ',2)
      if len(pair) == 2:
       group = pair[0].strip()
       ip = pair[1].strip()
       if (group != my_group):
         continue
       if NLAgentDB.ok_IP(ip):
         ip_list.append(ip)
  except IOError as e:
    print("Some input IPs skipped. Propper input format is 'group ip' in input file, one per line.")
   
  return ip_list
  

parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--db', action="store", dest='filename', default='/tmp/NLAgent.db', help="Database file containing host, connection and event tables.")
parser.add_argument('--master_start', action="store", dest='master_start_time', default=0)


def enum(**enums):
  return type('Enum', (), enums)
State = enum(RECV=1, WAIT=2, SEND=3, EXPECTED_WAIT=4, DONE=5)

def do_recv(s,v):
 #print("Waiting to receive ",v);
 s.setblocking(1)
 received = 0
 start = time.time()
 received = 0
 while True:
  if received == v:
   break
  if received > 0 and time.time()-start > TIMEOUT:
   break
  elif time.time() - start > TIMEOUT*2:
   break
  try:
   data = s.recv(8192)
   if data:
    received += len(data) 
    start = time.time()
   else:
    sleep(0.1)
  except:
   pass
 #print("Received ", received," bytes")
 return

def do_wait(s,v):
 #print("Sleeping for ", v/1000, "seconds.")
 time.sleep(v/1000)
 return
 
def do_send(s,v):
 #print("Sending ", v, "bytes of data")
 sent = 0
 try:
  s.sendall(b'a'*v) 
 finally: pass
 return
 
def expected_wait(s,v):
 #print("Expecting to wait for ", v, "seconds")
 return
 
def do_close(s,v):
 #s.close()
 #print("Done.")
 return

state_functions = { State.RECV : do_recv,
             State.WAIT : do_wait,
             State.SEND : do_send,
             State.EXPECTED_WAIT : expected_wait,
             State.DONE : do_close
           }




def db_connect(f):
 db = NLAgentDB.NLAgentDB(f)
 return db


def get_ips_event_list(db,ip,conn_id):
 # Double check the ip is in the connection.
 conn = db.get_connection(conn_id)
 if conn['src'] != ip and conn['dst'] != ip:
  print("ERROR: ",ip," is not involved in connection ",src,"->",dst," (id=", conn_id,")")
  return None
 # Get the host delays (for now we subtract these from the wait times. 
 if conn['src'] == ip:
  my_delay = db.get_host_delay(ip)
  their_delay = db.get_host_delay(conn['dst'])
 else:
  my_delay = db.get_host_delay(conn['dst'])
  their_delay = db.get_host_delay(ip)
 
 event_list = db.get_event_list(conn_id)
 event_queue = deque([])
 for event in event_list:
  #print ("Event for ", event['actor_ip'], " state ", event['state']);
  if event['actor_ip'] == ip:
   if event['state'] == 'WAIT':
    v = float(event['value'])
    if v > 1:
     e = {'state':State.WAIT, 'value':float(v)/1000}
     #print(ip, " will wait for ", v);
     event_queue.append(e)
   if event['state'] == 'SEND':
    e = {'state':State.SEND, 'value':int(event['value'])}
    event_queue.append(e)
    #print(ip, " will send ", int(event['value']));

  else:
   if event['state'] == 'WAIT':
    v = float(event['value'])

    if v > 1:
     e = {'state':State.EXPECTED_WAIT, 'value':float(v)/1000}
     event_queue.append(e)
     #print(ip, " will wait for ", v);

   if event['state'] == 'SEND':
    e = {'state':State.RECV, 'value':int(event['value'])}
    event_queue.append(e)
    #print(ip, " will receive ", int(event['value']));

 # For now, it's always the client that sends the done.
 if conn['src'] == ip:
  e = {'state':State.DONE, 'value':0}
  event_queue.append(e)
  #print (ip, " will now close connection");
 return event_queue
 


def handle_communication(db, sock, master_start_time, buffsize=1024, conn_id=None):
 my_name, my_port = sock.getsockname()
 their_name, their_port = sock.getpeername()

 # Were we given a conn_id? If not, we're the server.
 if conn_id == None:
  conn_id = db.get_connection_id(their_name, their_port, my_name, my_port, time.time()-float(master_start_time))
  #print("To me ", my_name, ":", my_port, " from ", their_name, ":", their_port)
 #else:
  #print("From me ", my_name, ":", my_port, " from ", their_name, ":", their_port)
 if conn_id == None:
  #print("ERROR: Unable to find connection ID for", my_name, " and ", their_name)
  return
 
 event_list = get_ips_event_list(db,my_name,conn_id)
 for event in event_list:
  state_functions[event['state']](sock,event['value'])
 return
   
def get_quit_time(db_filename):
 db = NLAgentDB.NLAgentDB(db_filename)
 q = db.get_last_connection_time()
 db.close()
 return q
